﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Accona.Remoting.Remoting
{
    /// <summary>
    /// Waiterqueue holds all waiting object. When a query is made to the exchange, the message is added to the waiterQueue as a WaitObject and the calling thread will be waiting for a reply. 
    /// When a reply is recieved, the WaitObject we set the reply on the WaitObject an notifies the waiting thread.
    /// </summary>
    public class WaiterQueue
    {
        #region [- Private fields -]

        private const int DefaultTimeout = 30000;
        private readonly List<WaiterObject> queue = new List<WaiterObject>();

        #endregion [- Private fields -]

        //Notifies the queue of the received message
        public bool NotifyMessage(DeserializedObject message)
        {
            Monitor.Enter(this);
            try
            {
                // If this was a reply message, return true
                WaiterObject wo = queue.Where(w => w.IsReply(message)).FirstOrDefault();
                if (wo != null)
                    wo.SetReplyObject(message.Value);
                return wo != null;
            }
            finally
            {
                Monitor.Exit(this);
            }
        }


        public object Query(ISender sender, byte[] data, int timeout, Guid callId)
        {
            WaiterObject wo;
            Monitor.Enter(this);
            try
            {
                wo = new WaiterObject {CallId = callId};
                queue.Add(wo);
            }
            finally
            {
                Monitor.Exit(this);
            }

            wo.WaitForReply(sender, data, timeout);

            Monitor.Enter(this);
            try
            {
                // Wait for reply. If InvalidCommand is received, an exception will be thrown                
                queue.Remove(wo);
                return wo.ReplyObject;
            }
            finally
            {
                Monitor.Exit(this);
            }
        }
    }

    /// <summary>
    /// This class is used by the WaiterQueue and holds information about the request as well as the reply.
    /// </summary>
    internal class WaiterObject
    {
        protected Guid callId;
        protected ManualResetEvent evt = new ManualResetEvent(false);
        protected Object replyObject;
        private ISender sender;


        public object ReplyObject
        {
            get { return replyObject; }
        }

        public Guid CallId
        {
            get { return callId; }
            set { callId = value; }
        }

        public virtual void SetReplyObject(Object obj)
        {
            replyObject = obj;
            evt.Set();
        }


        internal void WaitForReply(ISender sender, byte[] data, int timeout)
        {
            this.sender = sender;
            this.sender.Send(data);
            evt.WaitOne(timeout);
        }

        public virtual bool IsReply(DeserializedObject deserializedObject)
        {
            return deserializedObject.CallId == callId;
        }
    }
}